class_name Ext
extends Node

var query: QueryManager
const component_name := "Ext"
var _notify: Notify

var is_round = true
var camp_m: Multi  #Multi<CampC>
var data_m: Multi  #Multi<DataC>


func is_alive(fid) -> bool:
	var hpC = query.fetch_data("HpC") as Multi
	var hp = hpC.get(fid) as HpC
	return hp.value > 0


#todo
func exec_condition(_fighter, _buff):
	return true


func get_buffs(skill, fighter, _target, type):
	var buff_lib = query.fetch_single("BuffLib").data as BuffLib
	var id_list = skill.get("%s%s" % [type, C.SkillAttrKey.BuffID])
	if not id_list:
		return null
	if id_list is Array:
		var buff_list = []
		for buff_id in id_list:
			var buff = Buff.new(buff_lib.map.get(buff_id), fighter, skill)
			buff_list.append(buff)
		return buff_list
	return [Buff.new(buff_lib.map.get(id_list), fighter, skill)]


func calc_skill_hit(skill, fighter, target):
	var ratio = skill.get(C.SkillAttrKey.hitRate)
	if not ratio:
		return true
	if ratio == -1:
		return true

	var r = randi() % 100 + 1
	ensure_data_catch()
	var f_data = data_m.get(fighter) as DataC
	var t_data = data_m.get(target) as DataC
	var final_ratio = f_data.hit_ratio - t_data.dodge_ratio + ratio
	return r <= final_ratio


func get_round_order(fid) -> int:
	var round_orde_c = query.fetch_data("RoundOrderC")
	var r = round_orde_c.get(fid)
	return r.value if r else 0


func auto_skill(fighter):
	var lib = query.fetch_data("SkillLibC").get(fighter)

	for skill_id in lib.map:
		var skill = lib.map[skill_id] as SkillCmdC.Skill
		if is_cooldown(fighter, skill.next_time) or skill.get("passive"):
			continue
		var cmd = SkillCmdC.new()
		cmd.value = SkillCmdC.SkillCmd.new(skill_id)
		return [cmd]


func is_cooldown(_fighter, next_time):
	var frame = query.fetch_single("FrameTime").data as FrameTime
	return frame.count < next_time


func get_next_time(_fighter, cooldown):
	var frame = query.fetch_single("FrameTime").data as FrameTime
	return frame.count + (cooldown / frame.frame_interval | 0)


func is_round_cooldown(fighter, next_time):
	var round_c = query.fetch_single("RoundCountC").get(fighter) as RoundCount
	return round_c.value < next_time


func get_round_next_time(fighter, cooldown):
	var round_c = query.fetch_single("RoundCountC").get(fighter) as RoundCount
	return round_c.value + cooldown


func skill_select(skill: SkillCmdC.Skill, fighter: int, cmd: SkillCmdC.SkillCmd):
	var SkillAttrKey = C.SkillAttrKey
	var tt = skill.get(SkillAttrKey.targetType)
	if not tt:
		tt = cmd.target1
	var skill_args = skill.get(SkillAttrKey.targetTypeArgs)
	var args = skill_args if skill_args else cmd.target2
	var r = []
	var _camp_m = query.fetch_component("CampC")
	var pos_m = query.fetch_component("PositionC")
	var hp_m = query.fetch_component("HpC")
	var entities = query.entities.iter()
	select(fighter, tt, args, entities, _camp_m, pos_m, hp_m, r)
	return r


func select(fighter: int, target_type, arg, fighters, _camp_m, pos_m, hp_m, result):
	var type = C.TargetType.get(target_type)
	if not type:
		return false
	if type == C.TargetType.self:
		result.append(fighter)
		return true
	var camp_func = "diff_camp_func"
	if type <= C.TargetType.friendN:
		camp_func = "same_camp_func"
	elif type <= C.TargetType.ownSideN:
		camp_func = "ownside_camp_func"
		type -= 20
	elif type < 100:  #only lower than 100 will -40
		type -= 40
	select_func(fighter, type, arg, camp_func, fighters, hp_m, pos_m, _camp_m, result)
	return true


func select_func(fighter, type, arg, camp_func, fighters, hp_m, pos_m, _camp_m, result):
	var camp = _camp_m.get(fighter).value
	var mm = C.MAX_SAFE_INTEGER
	var target = 0
	var start = result.size()
	match type:
		C.TargetType.base:
			var list = []
			for f in fighters:
				if self.call(camp_func, fighter, camp, f) && is_alive(f):
					list.append(f)
			if not list:
				return
			list.sort_custom(self, "hatred_sort")
			result.append(list[0])
		C.TargetType.friendAll:
			for f in fighters:
				if self.call(camp_func, fighter, camp, f) && is_alive(f):
					result.append(f)
		C.TargetType.friendMinHp:
			for f in fighters:
				if self.call(camp_func, fighter, camp, f):
					var hp = hp_m.get(f)
					if hp && hp.value > 0 && hp.value < mm:
						mm = hp.value
						target = f
			if target:
				result.append(target)
		C.TargetType.friendMaxHp:
			mm = 0
			for f in fighters:
				if self.call(camp_func, fighter, camp, f):
					var hp = hp_m.get(f)
					if hp && hp.value > mm:
						mm = hp.value
						target = f
			if target:
				result.append(target)
		C.TargetType.friendNear:
			var pos = pos_m.get(fighter)
			if pos && pos.value:
				for f in fighters:
					if self.call(camp_func, fighter, camp, f) && is_alive(f):
						var p = pos_m.get(f)
						if p && p.value:
							var d = distance2(pos.value, p.value)
							if d < mm:
								mm = d
								target = f
				if target:
					result.append(target)

		C.TargetType.friendRound:
			var pos = pos_m.get(fighter)
			if pos && pos.value:
				var d2 = arg * arg
				for f in fighters:
					if self.call(camp_func, fighter, camp, f) && is_alive(f):
						var p = pos_m.get(f)
						if p && p.value:
							var d = distance2(pos.value, p.value)
							if d < d2:
								result.append(f)
		C.TargetType.friend1:
			for f in fighters:
				if self.call(camp_func, fighter, camp, f) && is_alive(f):
					result.append(f)
			selectN(1, start, result)
		C.TargetType.friend2:
			for f in fighters:
				if self.call(camp_func, fighter, camp, f) && is_alive(f):
					result.append(f)
			selectN(2, start, result)
		C.TargetType.friend3:
			for f in fighters:
				if self.call(camp_func, fighter, camp, f) && is_alive(f):
					result.append(f)
			selectN(3, start, result)
		C.TargetType.friendN:
			for f in fighters:
				if self.call(camp_func, fighter, camp, f) && is_alive(f):
					result.append(f)
					arg -= 1
					if not arg:
						break
			selectN(arg, start, result)


func ensure_camp_catch():
	if camp_m:
		return
	camp_m = query.fetch_component("CampC")


func ensure_data_catch():
	if data_m:
		return
	data_m = query.fetch_component("DataC")


func diff_camp_func(_fighter, camp, target):
	ensure_camp_catch()
	var camp_t = camp_m.get(target) as CampC
	return camp_t && camp_t.value != camp


func same_camp_func(fighter, camp, target):
	ensure_camp_catch()
	if target == fighter:
		return false
	var camp_t = camp_m.get(target) as CampC
	return camp_t && camp_t.value == camp


func ownside_camp_func(_fighter, camp, target):
	ensure_camp_catch()
	var camp_t = camp_m.get(target) as CampC
	return camp_t && camp_t.value == camp


func distance2(a: Array, b: Array):
	var d = 0
	for i in range(a.size() - 1, -1, -1):
		var x = a[i] - b[i]
		d += x * x
	return d


func selectN(n, start, result: Array):
	n += start
	var end = result.size() - 1
	if n > end:
		return
	while start < n:
		var i = next_int(start, end)
		if i > start:
			var temp = result[start]
			result[start] = result[i]
			result[i] = temp
		start += 1
	result.resize(start)


func next_int(start, end):
	return randi() % end + start


func hatred_sort(a: int, b: int):
	ensure_data_catch()
	var da = data_m.get(a) as DataC
	var db = data_m.get(b) as DataC
	if da.hatred != db.hatred:
		return da.hatred < db.hatred
	return da._id < db._id
